package ch.iec.tc57._2011.meterreadings.clientsample;

import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.ws.Holder;

import org.w3c.dom.Node;

import ch.iec.tc57._2011.getmeterreadingsmessage.GetMeterReadingsRequestType;
import ch.iec.tc57._2011.getmeterreadingsmessage.MeterReadingsPayloadType;
import ch.iec.tc57._2011.meterreadings.FaultMessage;
import ch.iec.tc57._2011.meterreadings.GetMeterReadingsPort;
import ch.iec.tc57._2011.meterreadings.MeterReading;
import ch.iec.tc57._2011.meterreadings.MeterReading.IntervalBlocks;
import ch.iec.tc57._2011.meterreadings.MeterReading.IntervalBlocks.IntervalReadings;
import ch.iec.tc57._2011.meterreadings.MeterReading.Readings;
import ch.iec.tc57._2011.meterreadings.MeterReadings;
import ch.iec.tc57._2011.meterreadings.ReadingType;
import ch.iec.tc57._2011.meterreadings.clientsample.beans.ListResult;
import ch.iec.tc57._2011.meterreadings.clientsample.beans.Pair;
import ch.iec.tc57._2011.schema.message.HeaderType;
import ch.iec.tc57._2011.schema.message.MessageProperty;
import ch.iec.tc57._2011.schema.message.OptionType;
import ch.iec.tc57._2011.schema.message.ReplyType;
import ch.iec.tc57._2011.schema.message.UserType;

import com.primestone.cim.util.CollectionUtil;
import com.primestone.cim.util.DateUtil;
import com.primestone.cim.util.StringUtil;
import com.sun.org.apache.xerces.internal.dom.ElementNSImpl;

/**
 * @author cesar.mateus
 * May 5, 2013 4:21:04 PM
 * ClientSample.java
 * GetMeterReadingsClient
 * 
 */
public class ClientSample {

	private final static Logger LOGGER = Logger.getLogger(ClientSample.class.getName());
	
	/**
	 * @param port1
	 * @param yearIni
	 * @param monthIni
	 * @param dayIni
	 * @param yearEnd
	 * @param monthEnd
	 * @param dayEnd
	 * @param isM
	 * @param isR
	 * @param isV
	 * @param descVar
	 * @param socket
	 * @param device
	 * @param top
	 * @param onlyNew
	 * @return
	 * @throws FaultMessage
	 */
	public static String doRequestWithoutTicket(GetMeterReadingsPort port1, 
			int yearIni, int monthIni, int dayIni, int yearEnd, int monthEnd, int dayEnd,
			boolean isM, boolean isR, boolean isV, String descVar, String socket, String device, Integer top, Boolean onlyNew) throws FaultMessage {
		return doRequestWithoutTicket(port1, yearIni, monthIni, dayIni, yearEnd, monthEnd, dayEnd, isM, isR, isV, descVar, socket, device, top, onlyNew, false);
	}

	/**
	 * 
	 * @param port1
	 * @param yearIni
	 * @param monthIni
	 * @param dayIni
	 * @param yearEnd
	 * @param monthEnd
	 * @param dayEnd
	 * @param isM
	 * @param isR
	 * @param isV
	 * @param descVar
	 * @param socket
	 * @param device
	 * @param top
	 * @param oldBlock
	 * @param onlyNew
	 * @param isBatch 
	 * @return
	 * @throws FaultMessage
	 */
	public static String doRequestWithoutTicket(GetMeterReadingsPort port1, 
			int yearIni, int monthIni, int dayIni, int yearEnd, int monthEnd, int dayEnd,
			boolean isM, boolean isR, boolean isV, String descVar, String socket, String device, Integer top, Boolean onlyNew, Boolean isBatch) throws FaultMessage {
		String ticket = null;
		try {
			Holder<HeaderType> header = new Holder<HeaderType>();
			header.value = composeHeaderType();

			GetMeterReadingsRequestType request = composeRequest(
					new GregorianCalendar(yearIni, monthIni, dayIni),
					new GregorianCalendar(yearEnd, monthEnd, dayEnd),
					isM, isR, isV, descVar, socket, device, String.valueOf(top), null, onlyNew, isBatch );

			Holder<MeterReadingsPayloadType> payload = new Holder<MeterReadingsPayloadType>();

			Holder<ReplyType> reply = new Holder<ReplyType>();

			port1.getMeterReadings(header, request, payload, reply);

			if( reply.value != null ) {
				LOGGER.log(Level.INFO, "--- Reply was obtained: {0}", reply.value.getResult());
			}

			if( header.value != null ) {
				ticket = extractTicket(header);
			}

			//printPayload(payload);

		} catch (FaultMessage e) {
			e.printStackTrace();
			throw e;
		}
		return ticket;
	}

	/**
	 * @param port1
	 * @param startTime
	 * @param isM
	 * @param isR
	 * @param isV
	 * @param device
	 * @param top
	 * @param onlyNew
	 * @param revision
	 * @param context
	 * @param source
	 * @param userID
	 * @param pwd
	 * @param organization
	 * @return
	 * @throws FaultMessage
	 */
	public static String doRequestWithoutTicket(GetMeterReadingsPort port1, 
			GregorianCalendar startTime, boolean isM, boolean isR, boolean isV, String device, Integer top, Boolean onlyNew,
			String revision, String context, String source, String userID, String pwd, String organization) throws FaultMessage {
		return doRequestWithoutTicket(port1, startTime, isM, isR, isV, device, top, onlyNew, revision, context, source, userID, pwd, organization, false);
	}

	/**
	 * @param port1
	 * @param startTime
	 * @param isM
	 * @param isR
	 * @param isV
	 * @param device
	 * @param top
	 * @param onlyNew
	 * @param revision
	 * @param context
	 * @param source
	 * @param userID
	 * @param pwd
	 * @param organization
	 * @param isBatch 
	 * @return
	 * @throws FaultMessage
	 */
	public static String doRequestWithoutTicket(GetMeterReadingsPort port1, 
			GregorianCalendar startTime, boolean isM, boolean isR, boolean isV, String device, Integer top, Boolean onlyNew,
			String revision, String context, String source, String userID, String pwd, String organization, Boolean isBatch) throws FaultMessage {
		String ticket = null;
		try {
			Holder<HeaderType> header = new Holder<HeaderType>();
			header.value = composeHeaderType(null, revision, context, source, userID, pwd, organization);

			GetMeterReadingsRequestType request = composeRequest(
					(DateUtil.safe(startTime)) ? startTime : DateUtil.MIN_DATE_CAL,
							null, isM, isR, isV, null, null, device, String.valueOf(top), null, onlyNew, isBatch );

			Holder<MeterReadingsPayloadType> payload = new Holder<MeterReadingsPayloadType>();

			Holder<ReplyType> reply = new Holder<ReplyType>();

			port1.getMeterReadings(header, request, payload, reply);

			if( reply.value != null ) {
				LOGGER.log(Level.INFO, "--- Reply was obtained: {0}", reply.value.getResult());
			}

			if( header.value != null ) {
				ticket = extractTicket(header);
			}

			//printPayload(payload);

		} catch (FaultMessage e) {
			e.printStackTrace();
			throw e;
		}
		return ticket;
	}


	/**
	 * @param port1
	 * @param ticket
	 * @param socket
	 * @param device
	 * @return
	 * @throws FaultMessage
	 */
	public static Object[] doRequestWithTicket(GetMeterReadingsPort port1, String ticket, String socket, String device) throws FaultMessage {	
		return doRequestWithTicket(port1, ticket, socket, device, false);
	}


	/**
	 * 
	 * @param port1
	 * @param ticket
	 * @param socket 
	 * @param device 
	 * @param isBatch 
	 * @return
	 * @throws FaultMessage
	 */
	public static Object[] doRequestWithTicket(GetMeterReadingsPort port1, String ticket, String socket, String device, Boolean isBatch) throws FaultMessage {			
		//String res = null;
		String extractIdBlock = null;
		String extractLastBlock = null;
		String result = null;
		Holder<MeterReadingsPayloadType> payload = new Holder<MeterReadingsPayloadType>();
		try {
			Holder<HeaderType> header = new Holder<HeaderType>();
			header.value = composeHeaderType(ticket);

			GetMeterReadingsRequestType request = composeRequest(null, socket, device, isBatch);

			Holder<ReplyType> reply = new Holder<ReplyType>();

			port1.getMeterReadings(header, request, payload, reply);

			if( reply.value != null ) {
				LOGGER.log(Level.INFO, "--- Reply was obtained: {0}", reply.value.getResult());				
			}

			//res = printPayload(payload);
			ListResult listResult = extractListResult(reply.value);
			ArrayList<Pair> pairs = listResult.getPairs();
			for (Pair pair : pairs) {
				if (pair.getName().equals(StringUtil.ID_BLOCK)) {
					extractIdBlock = pair.getValue();
				} else if (pair.getName().equals(StringUtil.LAST_BLOCK)) {
					extractLastBlock = pair.getValue();
				}
			}

			result = reply.value.getResult();
		} catch (FaultMessage e) {
			e.printStackTrace();
			throw e;
		}
		return new Object[]{payload.value, extractIdBlock, extractLastBlock, result};
	}

	/**
	 * @param port1
	 * @param ticket
	 * @param oldBlock
	 * @param socket
	 * @param device
	 * @return
	 * @throws FaultMessage
	 */
	public static Object[] doRequestWithTicket(GetMeterReadingsPort port1, String ticket, Long oldBlock, String socket, String device) throws FaultMessage {
		return doRequestWithTicket(port1, ticket, oldBlock, socket, device, false);
	}

	/**
	 * 
	 * @param port1
	 * @param ticket
	 * @param oldBlock
	 * @param socket 
	 * @param device 
	 * @param isBatch 
	 * @return
	 * @throws FaultMessage
	 */
	public static Object[] doRequestWithTicket(GetMeterReadingsPort port1, String ticket, Long oldBlock, String socket, String device, Boolean isBatch) throws FaultMessage {			
		//String res = null;
		String extractIdBlock = null;
		String extractLastBlock = null;
		String result = null;
		Holder<MeterReadingsPayloadType> payload = new Holder<MeterReadingsPayloadType>();
		try {
			Holder<HeaderType> header = new Holder<HeaderType>();
			header.value = composeHeaderType(ticket);

			GetMeterReadingsRequestType request = composeRequest(String.valueOf(oldBlock), socket, device, isBatch);

			Holder<ReplyType> reply = new Holder<ReplyType>();

			port1.getMeterReadings(header, request, payload, reply);

			if( reply.value != null ) {
				LOGGER.log(Level.INFO, "--- Reply was obtained: {0}", reply.value.getResult());				
			}

			//res = printPayload(payload);

			ListResult listResult = extractListResult(reply.value);
			ArrayList<Pair> pairs = listResult.getPairs();
			for (Pair pair : pairs) {
				if (pair.getName().equals(StringUtil.ID_BLOCK)) {
					extractIdBlock = pair.getValue();
				} else if (pair.getName().equals(StringUtil.LAST_BLOCK)) {
					extractLastBlock = pair.getValue();
				}
			}

			result = reply.value.getResult();
		} catch (FaultMessage e) {
			e.printStackTrace();
			throw e;
		}
		return new Object[]{payload.value, extractIdBlock, extractLastBlock, result};
	}

	private static XMLGregorianCalendar getXMLGregorianCalendarFromGregorianCalendar() {
		//		String nowString;
		//		GregorianCalendar nowGregorianCalendar = null;
		//		try {
		//			nowString = ISO8601.fromCalendar(Calendar.getInstance());
		//			nowGregorianCalendar = ISO8601.toGregorianCalendar(nowString);
		//		} catch (ParseException e) {
		//			e.printStackTrace();
		//		}

		XMLGregorianCalendar xmlGregorianCalendar = null;
		try {
			//xmlGregorianCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(nowGregorianCalendar);
			xmlGregorianCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(new GregorianCalendar());
		} catch (DatatypeConfigurationException e) {
			e.printStackTrace();
		}
		return xmlGregorianCalendar;
	}

	/**
	 * 
	 * @param gregorianCalendar
	 * @return
	 */
	private static XMLGregorianCalendar getXMLGregorianCalendarFromGregorianCalendar(GregorianCalendar gregorianCalendar) {
		//		String nowString;
		//		GregorianCalendar nowGregorianCalendar = null;
		//		try {
		//			nowString = ISO8601.fromCalendar(Calendar.getInstance());
		//			nowGregorianCalendar = ISO8601.toGregorianCalendar(nowString);
		//		} catch (ParseException e) {
		//			e.printStackTrace();
		//		}

		XMLGregorianCalendar xmlGregorianCalendar = null;
		try {
			//xmlGregorianCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(nowGregorianCalendar);
			xmlGregorianCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(gregorianCalendar);
		} catch (DatatypeConfigurationException e) {
			e.printStackTrace();
		}
		return xmlGregorianCalendar;
	}

	private static String extractTicket(Holder<HeaderType> header) {
		if (header.value != null) {
			List<MessageProperty> properties = header.value.getProperty();
			for (MessageProperty messageProperty : properties) {
				if(messageProperty.getName().equals("ticket")) {
					return messageProperty.getValue();
				}
			}
		}
		return null;
	}

	//	private static void putTicket(Holder<HeaderType> header, String ticket) {
	//		if (header.value != null) {			
	//			MessageProperty messageProperty = new MessageProperty();
	//			messageProperty.setName("ticket");
	//			messageProperty.setValue(ticket);
	//			header.value.getProperty().add(messageProperty);			
	//		}
	//	}

	private static HeaderType composeHeaderType() {
		return composeHeaderType(null, "1", "TESTING", "PrimeStone", "CIM", "CIM", "Pstone");
	}

	private static HeaderType composeHeaderType(String ticket) {
		HeaderType headerType = composeHeaderType();
		MessageProperty mpt = new MessageProperty();
		mpt.setName("ticket");
		mpt.setValue(ticket);
		headerType.getProperty().add(mpt);
		return headerType;
	}

	private static HeaderType composeHeaderType(String ticket, String revision, String context, String source, String userID, String pwd, String organization) {
		HeaderType headerType = new HeaderType();
		headerType.setNoun("MeterReading");
		headerType.setVerb("get");
		headerType.setRevision(revision);
		headerType.setContext(context);
		headerType.setTimestamp(getXMLGregorianCalendarFromGregorianCalendar());
		headerType.setSource(source);
		UserType userType = new UserType();
		userType.setUserID(userID);
		userType.setOrganization(organization);
		headerType.setUser(userType);

		MessageProperty messageProperty = new MessageProperty();
		messageProperty.setName("pwd");
		messageProperty.setValue(pwd);
		headerType.getProperty().add(messageProperty);

		if (ticket != null && ticket.length() > 0) {
			MessageProperty mpt = new MessageProperty();
			mpt.setName("ticket");
			mpt.setValue(ticket);
			headerType.getProperty().add(mpt);
		}

		return headerType;
	}

	//	private static GetMeterReadingsRequestType composeRequest() {
	//		GetMeterReadingsRequestType request = new GetMeterReadingsRequestType();
	//		request.setStartTime(getXMLGregorianCalendarFromGregorianCalendar(new GregorianCalendar(2009, 7, 1)));
	//		//request.setEndTime(getXMLGregorianCalendarFromGregorianCalendar(new GregorianCalendar(2009, 8, 1)));
	//		OptionType optionSocket = new OptionType();
	//		optionSocket.setName("socket");
	//		optionSocket.setValue("prueba");
	//		OptionType optionDescVar = new OptionType();
	//		optionDescVar.setName("descvar");
	//		optionDescVar.setValue("KWH");
	//		OptionType optionVarType = new OptionType();
	//		optionVarType.setName("vartype");
	//		optionVarType.setValue("MR");
	//		request.getOption().add(optionSocket);
	//		request.getOption().add(optionDescVar);
	//		request.getOption().add(optionVarType);
	//		return request;
	//	}


	private static GetMeterReadingsRequestType composeRequest(GregorianCalendar gCalIni, GregorianCalendar gCalEnd,
			boolean isM, boolean isR, boolean isV, String descVar, String socket, String device, String top, String oldBlock, Boolean onlyNew, Boolean isBatch) {
		GetMeterReadingsRequestType request = new GetMeterReadingsRequestType();

		if (gCalIni != null) {
			request.setStartTime(getXMLGregorianCalendarFromGregorianCalendar(gCalIni));
		}

		if (gCalEnd != null) {
			request.setEndTime(getXMLGregorianCalendarFromGregorianCalendar(gCalEnd));	
		}

		if (socket != null) {
			OptionType optionSocket = new OptionType();
			optionSocket.setName("socket");
			optionSocket.setValue(socket);
			request.getOption().add(optionSocket);	
		}

		if (device != null) {
			OptionType optionDevice = new OptionType();
			optionDevice.setName("device");
			optionDevice.setValue(device);
			request.getOption().add(optionDevice);	
		}

		OptionType optionVarType = new OptionType();
		optionVarType.setName("vartype");
		optionVarType.setValue((isM?"M":"")+(isR?"R":"")+(isV?"V":""));
		request.getOption().add(optionVarType);

		if (descVar != null && descVar.length() > 0) {
			OptionType optionDescVar = new OptionType();
			optionDescVar.setName("descvar");
			optionDescVar.setValue(descVar);//KWH
			request.getOption().add(optionDescVar);
		}

		OptionType optionTop = new OptionType();
		optionTop.setName("top");
		optionTop.setValue(top);
		request.getOption().add(optionTop);

		if (oldBlock != null) {
			OptionType optionOldBlock = new OptionType();
			optionOldBlock.setName("oldBlock");
			optionOldBlock.setValue(oldBlock);
			request.getOption().add(optionOldBlock);
		}

		if (onlyNew != null && onlyNew.equals(true)) {
			OptionType optionOnlyNew = new OptionType();
			optionOnlyNew.setName("onlyNew");
			optionOnlyNew.setValue("T");
			request.getOption().add(optionOnlyNew);
		}

		if (isBatch.equals(true)) {
			OptionType optionBatch = new OptionType();
			optionBatch.setName("batch");
			optionBatch.setValue("T");
			request.getOption().add(optionBatch);
		}

		return request;
	}

	private static GetMeterReadingsRequestType composeRequest(String oldBlock, String socket, String device, Boolean isBatch) {
		GetMeterReadingsRequestType request = new GetMeterReadingsRequestType();
		if (oldBlock != null) {
			OptionType optionOldBlock = new OptionType();
			optionOldBlock.setName("oldBlock");
			optionOldBlock.setValue(oldBlock);
			request.getOption().add(optionOldBlock);
		}

		if (socket != null) {
			OptionType optionSocket = new OptionType();
			optionSocket.setName("socket");
			optionSocket.setValue(socket);
			request.getOption().add(optionSocket);	
		}

		if (device != null) {
			OptionType optionDevice = new OptionType();
			optionDevice.setName("device");
			optionDevice.setValue(device);
			request.getOption().add(optionDevice);	
		}

		if (isBatch != null && isBatch.equals(true)) {
			OptionType optionBatch = new OptionType();
			optionBatch.setName("batch");
			optionBatch.setValue("T");
			request.getOption().add(optionBatch);
		}

		return request;
	}

	private static String printPayload(Holder<MeterReadingsPayloadType> payload) {
		boolean done = false;
		StringBuilder builder = new StringBuilder();
		if (payload.value != null) {
			MeterReadingsPayloadType value = payload.value;
			MeterReadings meterReadings = value.getMeterReadings();
			List<MeterReading> meterReading = meterReadings.getMeterReading();
			List<IntervalBlocks> intervalBlocks;
			List<IntervalReadings> intervalReadings;
			List<Readings> readings;
			for (MeterReading mReading : meterReading) {
				done = true;
				intervalBlocks = mReading.getIntervalBlocks();
				for (IntervalBlocks intervalBlock : intervalBlocks) {
					builder.append("IntervalBlock: ").append("\n");
					intervalReadings = intervalBlock.getIntervalReadings();
					for (IntervalReadings intervalReading : intervalReadings) {
						builder.append(" TimeStamp: " + intervalReading.getTimeStamp())
						.append(", Value: " + intervalReading.getValue()).append("\n");
					}
					builder.append(" Ref: " + intervalBlock.getReadingType().getRef()).append("\n");
				}

				readings = mReading.getReadings();
				if (readings.size() > 0) {
					builder.append("Readings: ").append("\n");	
				}
				for (Readings reads : readings) {					
					builder.append(" TimeStamp: " + reads.getTimeStamp())
					.append(", Value: " + reads.getValue()).append("\n")
					.append(" Ref: " + reads.getReadingType().getRef()).append("\n");
				}
			}

			List<ReadingType> readingTypes = meterReadings.getReadingType();
			for (ReadingType readingType : readingTypes) {
				builder.append(" ReadingType: ").append("\n");
				builder.append(" MRID: " + readingType.getMRID()).append("\n");
				builder.append(" Alias: " + readingType.getAliasName()).append("\n");
			}
		}
		LOGGER.log(Level.FINE, "--- Builder: {0}", builder);
		return builder.toString().length() == 0 ? null : builder.toString();
	}

	private static ListResult extractListResult(ReplyType replyType) {
		ListResult listResult = new ListResult();
		if (CollectionUtil.safe(replyType.getAny())) {
			List<Object> any = replyType.getAny();
			try {
				ElementNSImpl principalElement = (ElementNSImpl)any.get(0);
				int length = principalElement.getLength();
				Pair pair = null;
				Node pairsNode;
				String pairsNodeName;
				ElementNSImpl eachPair;
				int lengthPartsPair;
				Node partPairNode;
				String partPairNodeName;
				for (int i = 0; i < length; i++) {
					pairsNode = principalElement.item(i);
					pairsNodeName = pairsNode.getNodeName();
					if (pairsNodeName.equals("pairs")) {
						eachPair = (ElementNSImpl)pairsNode;
						lengthPartsPair = eachPair.getLength();
						for (int j = 0; j < lengthPartsPair; j++) {
							partPairNode = eachPair.item(j);
							partPairNodeName = partPairNode.getNodeName();
							if (partPairNodeName.equals("name")) {
								pair = new Pair(partPairNode.getTextContent(), null);
							} else {
								if (pair != null) {
									pair.setValue(partPairNode.getTextContent());
									listResult.add(pair);	
								}
							}
						}
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		return listResult;
	}
}